package tr.edu.ege.kipler.common;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;

/**
 * Represents a node in a graph, i.e., {@link RelationalModel}.
 * 
 * @author İnanç Seylan
 */
public class RelationalNode<V extends RelationalValue<V, K>, R, K> implements
		Copyable<RelationalNode<V, R, K>> {
	private V value;

	private MultivalueMap<R, RelationalNode<V, R, K>> outRelMap = new MultivalueLinkedHashMap<R, RelationalNode<V, R, K>>();

	private MultivalueMap<R, RelationalNode<V, R, K>> inRelMap = new MultivalueLinkedHashMap<R, RelationalNode<V, R, K>>();

	public RelationalNode() {
	}

	/**
	 * @param value
	 */
	public RelationalNode(V value) {
		this.value = value;
	}

	public void addIncoming(R relation, RelationalNode<V, R, K> owner) {
		inRelMap.put(relation, owner);
		owner.outRelMap.put(relation, this);
	}

	public void addOutgoing(R relation, RelationalNode<V, R, K> filler) {
		outRelMap.put(relation, filler);
		filler.inRelMap.put(relation, this);
	}

	public boolean containsIncoming(RelationalNode<V, R, K> owner) {
		return inRelMap.containsValue(owner);
	}

	public boolean containsOutgoing(RelationalNode<V, R, K> filler) {
		return outRelMap.containsValue(filler);
	}

	public boolean containsIncoming(R rel, RelationalNode<V, R, K> owner) {
		return inRelMap.containsKeyValue(rel, owner);
	}

	public boolean containsOutgoing(R rel, RelationalNode<V, R, K> filler) {
		return outRelMap.containsKeyValue(rel, filler);
	}

	public RelationalNode<V, R, K> copy() {
		RelationalNode<V, R, K> node = new RelationalNode<V, R, K>();
		node.setValue(getValue().copy());
		return node;
	}

	private Set<R> edges(MultivalueMap<R, RelationalNode<V, R, K>> map,
			RelationalNode<V, R, K> node) {
		Set<R> result = new LinkedHashSet<R>();
		for (R key : map.keySet()) {
			if (map.containsKeyValue(key, node)) {
				result.add((R) key);
			}
		}
		return result;
	}

	public Set<R> edgesIncoming() {
		return inRelMap.keySet();
	}

	public Set<R> edgesIncoming(RelationalNode<V, R, K> from) {
		return edges(inRelMap, from);
	}

	public Set<R> edgesOutgoing() {
		return outRelMap.keySet();
	}

	public Set<R> edgesOutgoing(RelationalNode<V, R, K> to) {
		return edges(outRelMap, to);
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object obj) {
		if (obj != null && (obj.getClass().equals(this.getClass()))) {
			RelationalNode<V, R, K> other = (RelationalNode<V, R, K>) obj;
			return value.equals(other.getValue()) && incomingValuesEqual(other)
					&& outgoingValuesEqual(other);
		}
		return false;
	}

	private Set<RelationalNode<V, R, K>> get(
			MultivalueMap<R, RelationalNode<V, R, K>> map) {
		return map.values();
	}

	public Set<RelationalNode<V, R, K>> getIncoming() {
		return get(inRelMap);
	}

	public Set<RelationalNode<V, R, K>> getOutgoing() {
		return get(outRelMap);
	}

	private Set<RelationalNode<V, R, K>> getNamed(
			MultivalueMap<R, RelationalNode<V, R, K>> map, R relation) {
		return map.getValuesForKey(relation);
	}

	public Set<RelationalNode<V, R, K>> getIncoming(R relation) {
		return getNamed(inRelMap, relation);
	}

	public Set<RelationalNode<V, R, K>> getOutgoing(R relation) {
		return getNamed(outRelMap, relation);
	}

	public V getValue() {
		return value;
	}

	@Override
	public int hashCode() {
		return value.hashCode();
	}

	private boolean incomingValuesEqual(RelationalNode<V, R, K> otherNode) {
		edgesIncoming().equals(otherNode.edgesIncoming());
		boolean result = true;
		for (R rel : edgesIncoming()) {
			if (!valuesIncoming(rel).equals(otherNode.valuesIncoming(rel))) {
				result = false;
				break;
			}
		}
		return result;
	}

	private boolean outgoingValuesEqual(RelationalNode<V, R, K> otherNode) {
		edgesOutgoing().equals(otherNode.edgesOutgoing());
		boolean result = true;
		for (R rel : edgesOutgoing()) {
			if (!valuesOutgoing(rel).equals(otherNode.valuesOutgoing(rel))) {
				result = false;
				break;
			}
		}
		return result;
	}

	private void remove(MultivalueMap<R, RelationalNode<V, R, K>> map,
			RelationalNode<V, R, K> node) {
		List<R> toBeRemoved = new ArrayList<R>();
		for (R key : map.keySet()) {
			if (map.containsKeyValue(key, node)) {
				toBeRemoved.add(key);
			}
		}
		for (R rel : toBeRemoved) {
			map.remove(rel, node);
		}
	}

	public void removeIncoming(RelationalNode<V, R, K> owner) {
		remove(inRelMap, owner);
	}

	public void removeOutgoing(RelationalNode<V, R, K> filler) {
		remove(outRelMap, filler);
	}

	public void setValue(V value) {
		this.value = value;
	}

	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		buffer.append(getValue() + "\n");
		// for (BinaryRelation<RelationalNode<V, R, K>, R> rel : outRelSet) {
		// buffer.append(rel.getRelation().toString()
		// + " "
		// + ((RelationalNode<V, R, K>) rel.getOwner()).getValue()
		// .toString()
		// + " "
		// + ((RelationalNode<V, R, K>) rel.getFiller()).getValue()
		// .toString() + "\n");
		// }
		// for (BinaryRelation<RelationalNode<V, R, K>, R> rel : inRelSet) {
		// buffer.append(rel.getRelation().toString()
		// + " "
		// + ((RelationalNode<V, R, K>) rel.getOwner()).getValue()
		// .toString()
		// + " "
		// + ((RelationalNode<V, R, K>) rel.getFiller()).getValue()
		// .toString() + "\n");
		// }
		return buffer.toString();
	}

	private Set<V> valuesIncoming(R rel) {
		Set<V> result = new HashSet<V>();
		for (RelationalNode<V, R, K> node : getIncoming(rel)) {
			result.add(node.getValue());
		}
		return result;
	}

	private Set<V> valuesOutgoing(R rel) {
		Set<V> result = new HashSet<V>();
		for (RelationalNode<V, R, K> node : getOutgoing(rel)) {
			result.add(node.getValue());
		}
		return result;
	}

}
